package app

import (
	"embed"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"strings"

	"gitee.com/xuender/oils/logs"
	"gitee.com/xuender/reader/files"
	"gitee.com/xuender/reader/pb"
	"gitee.com/xuender/reader/watch"
	"github.com/gin-gonic/gin"
)

// App 应用.
type App struct {
	config *pb.Config
	ws     *watch.Service
	engine *gin.Engine
	fc     *files.Ctrl
}

// NewApp 新建应用.
func NewApp(
	config *pb.Config,
	www *embed.FS,
	ws *watch.Service,
	fc *files.Ctrl,
) *App {
	if config.Debug {
		logs.SetFlags(log.LstdFlags | log.Lshortfile)
		logs.SetLevel(logs.LevelDebug)
	} else {
		gin.SetMode(gin.ReleaseMode)
		logs.SetLevel(logs.LevelInfo)
	}

	e := gin.Default()

	e.Use(errHandler())
	e.Use(StaticHandler("/", www, "www"))

	return &App{
		config: config,
		ws:     ws,
		fc:     fc,
		engine: e,
	}
}

func (p *App) handleNotFound(ctx *gin.Context) {
	logs.Warn("not found", ctx.Request.URL)
	ctx.Redirect(http.StatusTemporaryRedirect, "/")
}

func errHandler() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				msg := &pb.Error{}
				switch e := err.(type) {
				case string:
					msg.Error = e
				case error:
					msg.Error = e.Error()
				default:
					msg.Error = fmt.Sprintf("%v", err)
				}

				ctx.ProtoBuf(http.StatusInternalServerError, msg)
				logs.ErrorStack(err)

				return
			}
		}()
		ctx.Next()
	}
}

func (p *App) init() {
	p.engine.NoMethod(p.handleNotFound)
	p.engine.NoRoute(p.handleNotFound)

	api := p.engine.Group("/api")

	api.GET("/ping", p.ping)
	api.GET("/config", p.getConfig)
	api.GET("/ws", p.ws.Websocket)
	p.fc.Router(api.Group("/file"))
}

func (p *App) getConfig(ctx *gin.Context) {
	ctx.ProtoBuf(http.StatusOK, p.config)
}

// Run 运行.
func (p *App) Run() {
	p.ws.Add(p.config.Paths...)
	p.config.Paths = p.ws.Paths.Slice()
	p.init()

	go p.ws.Run()
	p.info()
	logs.Panic(p.engine.Run(fmt.Sprintf(":%d", p.config.Port)))
}

func (p *App) info() {
	fmt.Fprintln(os.Stdout, "远程文件阅读器")
	fmt.Fprintf(os.Stdout, "\n本地访问 (http://localhost:%d)\n", p.config.Port)

	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return
	}

	for _, address := range addrs {
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				if strings.HasPrefix(ipnet.IP.String(), "172.17") {
					continue
				}

				fmt.Fprintf(os.Stderr, "网络访问 (http://%s:%d)\n", ipnet.IP, p.config.Port)
			}
		}
	}
}

// Close 关闭.
func (p *App) Close() {
	p.ws.Close()
}

func (p *App) ping(c *gin.Context) {
	c.String(http.StatusOK, "pong")
}
